﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Globalization;

namespace TWiStEr.Math {
	using Math = System.Math;
	[TypeConverter(typeof(VectorConverter))]
	/// <summary>
	/// A vector in Cartesian coordinate system.
	/// X is the left-right axis
	/// Y is the down-up axis
	/// Z is the back-front axis
	/// Directions given in -1 -> 1 order. eg.: (0,1,-1) is above and behind the origo
	/// 
	/// Also used to store polar coordinates:
	/// <list>
	/// <item>x is the radius</item>
	/// <item>y, z are the rotations around the y and x axes respectively</item>
	/// </list>
	/// </summary>
	public struct Vector : IFormattable {
		/// <summary>
		/// corrdinate
		/// </summary>
		public double X, Y, Z;
		public static readonly Vector UnitX = new Vector(1, 0, 0);
		public static readonly Vector UnitY = new Vector(0, 1, 0);
		public static readonly Vector UnitZ = new Vector(0, 0, 1);
		public static readonly Vector Zero = new Vector(0, 0, 0);
		#region Constructors
		public Vector(double x) : this(x, 0, 0) { }
		public Vector(double x, double y) : this(x, y, 0) { }
		public Vector(double x, double y, double z) { this.X = x; this.Y = y; this.Z = z; }
		public Vector(Vector v, double length) { X = v.X; Y = v.Y; Z = v.Z; Length = length; }
		#endregion
		#region operations creating new
		/// <summary>
		/// Gets or sets the Length of this Vector.
		/// The length of a vector is the square root of the sum of the components.
		/// Settings this means its length will be equal to 'value'.
		/// </summary>
		public double Length {
			get {
				return Math.Sqrt((X * X) + (Y * Y) + (Z * Z));
			}
			set {
				if (Math.Abs(value) < double.Epsilon) {
					X = 0; Y = 0; Z = 0;
				} else {
					double length = Length;
					X = X / length * value;
					Y = Y / length * value;
					Z = Z / length * value;
				}
			}
		}
		// returns the normalized vector
		public static Vector Normalize(Vector v) {
			return v / v.Length;
		}
		public static Vector Normal(Vector v1, Vector v2, Vector v3) {
			return Vector.Normalize((v2 - v1) ^ (v3 - v1));
		}
		public static Vector Abs(Vector v) {
			return new Vector(Math.Abs(v.X), Math.Abs(v.Y), Math.Abs(v.Z));
		}

		/// <summary>
		/// Creates a Cartesian vector from radius and polar angles
		/// </summary>
		/// <param name="radius">rho</param>
		/// <param name="angleX">phi</param>
		/// <param name="angleZ">theta</param>
		/// <returns></returns>
		public static Vector FromPolar(double radius, double angleY, double angleZ) {
			double x = radius * Math.Sin(angleY) * Math.Cos(angleZ);
			double y = radius * Math.Sin(angleY) * Math.Sin(angleZ);
			double z = radius * Math.Cos(angleY);
			return new Vector(x, y, z);
		}
		public static Vector FromPolar(Vector v) {
			return FromPolar(v.X, v.Y, v.Z);
		}
		public static Vector ToPolar(double x, double y, double z) {
			return ToPolar(new Vector(x, y, z));
		}
		public static Vector ToPolar(Vector v) {
			double radius = v.Length;
			// x => z, y => x, z => y
			double phi = Math.Atan2(Math.Sqrt(Math.Pow(v.Z, 2) + Math.Pow(v.X, 2)), v.Y);
			double theta = Math.Atan2(v.X, v.Z);
			return new Vector(radius, theta, phi);
		}
		#endregion

		#region operations on self
		public Vector Normalize() {
			double len = Length;
			if (len == 0) len = 1;
			Vector v = this / len;
			SetXYZ(v);
			return this;
		}
		public void SetXYZ(Vector v) {
			this.X = v.X;
			this.Y = v.Y;
			this.Z = v.Z;
		}
		#endregion
		#region V # V operators
		public static Vector operator +(Vector v1, Vector v2) {
			return new Vector(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
		}
		public static Vector operator -(Vector v1, Vector v2) {
			return new Vector(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z);
		}
		public static double operator *(Vector a, Vector b) {
			return DotProduct(a, b);
		}
		public static Vector operator ^(Vector a, Vector b) {
			return CrossProduct(a, b);
		}

		// Math.Cos(angle) = norm(a) dot norm(b) = (a dot b) / (|a| * |b|)
		// => angle = aMath.Cos(<expr_as_above>)
		// angle between two normalized vectors in radians
		public static double operator |(Vector a, Vector b) {
			return Math.Acos(a * b);
		}
		// angle between two arbitary vectors in radians
		public static double operator /(Vector a, Vector b) {
			return Math.Acos((a * b) / (a.Length * b.Length));
		}
		#endregion

		#region #V unary operators
		public static Vector operator -(Vector v) { return new Vector(-v.X, -v.Y, -v.Z); }
		#endregion

		#region V # scalar binary operators
		public static Vector operator /(Vector v, double scalar) {
			return new Vector(v.X / scalar, v.Y / scalar, v.Z / scalar);
		}
		public static Vector operator *(Vector v, double scalar) {
			return new Vector(v.X * scalar, v.Y * scalar, v.Z * scalar);
		}
		public static Vector operator +(Vector v, double scalar) {
			return new Vector(v.X + scalar, v.Y + scalar, v.Z + scalar);
		}
		public static Vector operator -(Vector v, double scalar) {
			return new Vector(v.X - scalar, v.Y - scalar, v.Z - scalar);
		}
		public static Vector operator *(double scalar, Vector v) {
			return v * scalar;
		}
		public static Vector operator +(double scalar, Vector v) {
			return v + scalar;
		}
		[Obsolete("Be careful, it makes no sense")]
		public static Vector operator -(double scalar, Vector v) {
			return -(v - scalar);
		}
		#endregion
		public override string ToString() {
			return string.Format("({0:0.##},{1:0.##},{2:0.##})", X, Y, Z);
		}

		/// <summary>
		/// Inner product of a and b.
		/// a.b = cos(theta), where theta is the angle between a and b.
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		public static double DotProduct(Vector a, Vector b) {
			return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
		}
		public static Vector CrossProduct(Vector a, Vector b) {
			return new Vector(
				a.Y * b.Z - a.Z * b.Y,
				a.Z * b.X - a.X * b.Z,
				a.X * b.Y - a.Y * b.X);
		}
		public static void AnglesBetween(Vector v1, Vector v2, out double deltaYRot, out double deltaXRot) {
			Vector diff = ToPolar(v1) - ToPolar(v2);
			deltaYRot = diff.Y;
			deltaXRot = diff.Z;
		}
		/// <summary>
		/// Returns the angle between two vectors.
		/// </summary>
		/// <param name="v1"></param>
		/// <param name="v2"></param>
		/// <returns>angle in radians</returns>
		public static double AngleBetween(Vector v1, Vector v2) {
			return Math.Acos(DotProduct(v1, v2) / (v1.Length * v2.Length));
		}

		/// <summary>
		/// Returns the angle between two vectors with given common starting point.
		/// </summary>
		/// <param name="c">Common starting point for vectors</param>
		/// <param name="v1">First vector's end point</param>
		/// <param name="v2">Second vector's end point</param>
		/// <returns>angle between two vectors in radians</returns>
		public static double AngleBetween(Vector c, Vector v1, Vector v2) {
			return AngleBetween(v1 - c, v2 - c);
		}

		/// <summary>
		/// Rotates the current vector by arbitrary angle around arbitrary axis.
		/// The rotation uses the Rodrigues formula.
		/// </summary>
		/// <param name="angle">The rotation angle in radians</param>
		/// <param name="axis">The rotation axis</param>
		/// <returns>this vector rotated around axis by angle radians</returns>
		public Vector Rotate(double angle, Vector axis) {
			return Math.Cos(angle) * this + (1 - Math.Cos(angle)) * Vector.DotProduct(this, axis) * axis
				+ Math.Sin(angle) * Vector.CrossProduct(axis, this);
		}

		public static Vector Parse(string source) {
			IFormatProvider cultureInfo = CultureInfo.InvariantCulture;
			string[] arr = source.Split(new[] { ',', ';', ' ', '\t', '/', '\\', '(', ')' }, StringSplitOptions.RemoveEmptyEntries);
			double[] vals = arr.Select(x => Convert.ToDouble(x, cultureInfo)).ToArray();
			switch (vals.Length) {
				case 0: return new Vector();
				case 1: return new Vector(vals[0]);
				case 2: return new Vector(vals[0], vals[1]);
				case 3: return new Vector(vals[0], vals[1], vals[2]);
				default:
					throw new NotSupportedException("Vector.Parse does not support more than 3 components");
			}
		}

		#region IFormattable Members
		public string ToString(string format, IFormatProvider formatProvider) {
			NumberFormatInfo instance = NumberFormatInfo.GetInstance(formatProvider);
			return string.Format(formatProvider, "{1:" + format + "}{0}{2:" + format + "}{0}{3:" + format + "}",
					instance.NumberDecimalSeparator.Length > 0 && ',' == instance.NumberDecimalSeparator[0] ? ';' : ',', X, Y, Z);
		}
		#endregion

		/// <summary>
		/// Projects this onto the parameter vector
		/// </summary>
		public Vector Project(Vector onto) {
			return DotProduct(this, onto) / Math.Pow(onto.Length, 2) * onto;
		}
		public Vector Perpendicular(Vector onto) {
			return this - this.Project(onto);
		}
	}
}
